package com.teejdeej.simpleneuralgame;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;

import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;

public class MainGame extends BasicGame {
	public static final int GridDimension = 12;
	public static final int ScreenWidth = 50;
	public static final int ScreenHeight = 50;
	public static final int UpdatesPerSecond = 16;
	public static final int ScanDistance = 20;
	public static final int FireDelay = 6;
	public static final double WallFactor = 0.06;
	
	public static final int BoardPossibilities = 4;
	public static final int BoardEmpty = 0;
	public static final int BoardEnemy = 1;
	public static final int BoardProjectileEnemy = 2;
	public static final int BoardWall = 3;
	
	public static final int NumberOfDirections = 8;
	public static final int DirectionUp = 0;
	public static final int DirectionDown = 1;
	public static final int DirectionLeft = 2;
	public static final int DirectionRight = 3;
	public static final int DirectionUpRight = 4;
	public static final int DirectionUpLeft = 5;
	public static final int DirectionDownRight = 6;
	public static final int DirectionDownLeft = 7;
	
	private Input m_input;
	private int m_accumulatedDelta;
	
	private int[] m_player0Position;
	private int[] m_player1Position;
	private Player m_player0Brain;
	private Player m_player1Brain;
	private int m_player0FireDelay;
	private int m_player1FireDelay;
	private List<int[]> m_player0ProjectilePositions;
	private List<Integer> m_player0ProjectileDirections;
	private List<int[]> m_player1ProjectilePositions;
	private List<Integer> m_player1ProjectileDirections;
	private HashSet<int[]> m_wallPositions;
	
	private int m_roundNumber;
	private List<int[][]> m_inputBoardSet;
	private List<int[]> m_inputPositionSet;
	private List<Move> m_outputSet;
	
	public MainGame() {
		super("Simple Neural Network Game");
	}

	@Override
	public void render(GameContainer arg0, Graphics arg1) throws SlickException {
		for(int x = 0; x < ScreenWidth; x++) for(int y = 0; y < ScreenHeight; y++)
		{
			arg1.setColor(new Color(255, 255, 255));
			arg1.drawRect(x * GridDimension, y * GridDimension, GridDimension, GridDimension);
		}
		
		arg1.setColor(new Color(0, 255, 0));
		arg1.fillOval(m_player0Position[0] * GridDimension + 1, m_player0Position[1] * GridDimension + 1, GridDimension - 1, GridDimension - 1);
		
		arg1.setColor(new Color(255, 0, 0));
		arg1.fillOval(m_player1Position[0] * GridDimension + 1, m_player1Position[1] * GridDimension + 1, GridDimension - 1, GridDimension - 1);
		
		for(int[] t_position : m_player0ProjectilePositions)
		{
			arg1.setColor(new Color(0, 255, 0));
			arg1.fillOval(t_position[0] * GridDimension + 4, t_position[1] * GridDimension + 4, GridDimension - 4, GridDimension - 4);
		}
		
		for(int[] t_position : m_player1ProjectilePositions)
		{
			arg1.setColor(new Color(255, 0, 0));
			arg1.fillOval(t_position[0] * GridDimension + 4, t_position[1] * GridDimension + 4, GridDimension - 4, GridDimension - 4);
		}
		
		for(int[] t_wallPosition : m_wallPositions)
		{
			arg1.setColor(new Color(0, 0, 255));
			arg1.fillRect(t_wallPosition[0] * GridDimension + 1, t_wallPosition[1] * GridDimension + 1, GridDimension - 1, GridDimension - 1);
		}
	}

	@Override
	public void init(GameContainer arg0) throws SlickException {		
		m_input = new Input(ScreenHeight * GridDimension);
		m_accumulatedDelta = 0;
		
		m_player0Position = new int[2];
		m_player0Position[0] = ScreenWidth / 4;
		m_player0Position[1] = ScreenHeight / 2;
		m_player0Brain = new RandomPlayer();
		m_player0FireDelay = 0;
		
		m_player1Position = new int[2];
		m_player1Position[0] = ScreenWidth / 4 * 3;
		m_player1Position[1] = ScreenHeight / 2;
		m_player1Brain = new NeuralNetAI();
		m_player1FireDelay = 0;
		
		m_player0ProjectilePositions = new ArrayList<int[]>();
		m_player0ProjectileDirections = new ArrayList<Integer>();
		m_player1ProjectilePositions = new ArrayList<int[]>();
		m_player1ProjectileDirections = new ArrayList<Integer>();
		
		m_wallPositions = new HashSet<int[]>();
		for(int x = 0; x < ScreenWidth; x++) for(int y = 0; y < ScreenHeight; y++)
		{
			if(x == 0 || x == ScreenWidth - 1 || y == 0 || y == ScreenHeight - 1)
			{
				int[] t_newWall = new int[2];
				t_newWall[0] = x;
				t_newWall[1] = y;
				m_wallPositions.add(t_newWall);
			}
		}
		
		Random t_random = new Random();
		
		for(int a = 0; a < WallFactor * (double) (ScreenWidth - 2) * (double) (ScreenHeight - 2); a++)
		{
			int[] t_newWall = new int[2];
			t_newWall[0] = 1 + t_random.nextInt(ScreenWidth - 2);
			t_newWall[1] = 1 + t_random.nextInt(ScreenHeight - 2);
			while(!isEmpty(t_newWall))
			{
				t_newWall[0] = 1 + t_random.nextInt(ScreenWidth - 2);
				t_newWall[1] = 1 + t_random.nextInt(ScreenHeight - 2);
			}
			m_wallPositions.add(t_newWall);
		}
		
		m_roundNumber = 1;
		m_inputBoardSet = new ArrayList<int[][]>();
		m_inputPositionSet = new ArrayList<int[]>();
		m_outputSet = new ArrayList<Move>();
	}

	@Override
	public void update(GameContainer arg0, int arg1) throws SlickException {
		if(m_input.isKeyDown(Input.KEY_ESCAPE))
			arg0.exit();
		
		m_accumulatedDelta += arg1;
		
		if(m_accumulatedDelta >= 1000 / UpdatesPerSecond)
		{
			m_accumulatedDelta = 0;
			
			if(m_player0FireDelay > 0)
				m_player0FireDelay--;
			
			if(m_player1FireDelay > 0)
				m_player1FireDelay--;
			
			int[][] t_player0Board = new int[ScreenWidth][ScreenHeight];
			
			for(int x = 0; x < ScreenWidth; x++) for(int y = 0; y < ScreenHeight; y++) t_player0Board[x][y] = BoardEmpty;
			
			t_player0Board[m_player1Position[0]][m_player1Position[1]] = BoardEnemy;
			
			for(int[] t_wallPosition : m_wallPositions)
			{
				t_player0Board[t_wallPosition[0]][t_wallPosition[1]] = BoardWall;
			}
			
			Move t_player0Move = m_player0Brain.move(t_player0Board, m_player0Position);
			
			if(t_player0Move.m_move)
			{
				int[] t_newPosition = m_player0Position.clone();
				
				switch(t_player0Move.m_moveDirection)
				{
				case DirectionUp:
					t_newPosition[1]--;
				break;
				
				case DirectionDown:
					t_newPosition[1]++;
				break;
				
				case DirectionLeft:
					t_newPosition[0]--;
				break;
				
				case DirectionRight:
					t_newPosition[0]++;
				break;
				}
				
				if(isEmpty(t_newPosition))
				{
					m_player0Position = t_newPosition;
				}
				else
				{
					t_player0Move.m_move = false;
				}
			}

			m_inputBoardSet.add(t_player0Board);
			m_inputPositionSet.add(m_player0Position);
			m_outputSet.add(t_player0Move);
			
			if(m_inputBoardSet.size() >= 100)
			{
				m_player1Brain.learn(m_inputBoardSet, m_inputPositionSet, m_outputSet);
				
				m_inputBoardSet.clear();
				m_inputPositionSet.clear();
				m_outputSet.clear();
			}
			
			int[][] t_player1board = new int[ScreenWidth][ScreenHeight];
			
			for(int x = 0; x < ScreenWidth; x++) for(int y = 0; y < ScreenHeight; y++) t_player1board[x][y] = BoardEmpty;
			
			t_player1board[m_player0Position[0]][m_player0Position[1]] = BoardEnemy;
			
			for(int[] t_wallPosition : m_wallPositions)
			{
				t_player1board[t_wallPosition[0]][t_wallPosition[1]] = BoardWall;
			}
			
			Move t_player1Move = m_player1Brain.move(t_player1board, m_player1Position);
			
			if(t_player1Move.m_move)
			{
				int[] t_newPosition = m_player1Position.clone();
				
				switch(t_player1Move.m_moveDirection)
				{
				case DirectionUp:
					t_newPosition[1]--;
				break;
				
				case DirectionDown:
					t_newPosition[1]++;
				break;
				
				case DirectionLeft:
					t_newPosition[0]--;
				break;
				
				case DirectionRight:
					t_newPosition[0]++;
				break;
				}
				
				if(isEmpty(t_newPosition))
				{
					m_player1Position = t_newPosition;
				}
			}
		}
	}
	
	public Boolean isEmpty(int[] pos)
	{
		if((pos[0] == m_player0Position[0] && pos[1] == m_player0Position[1]) || 
				(pos[0] == m_player1Position[0] && pos[1] == m_player1Position[1]))
			return false;
		
		for(int[] t_comparePos : m_wallPositions)
		{
			if(pos[0] == t_comparePos[0] && pos[1] == t_comparePos[1])
				return false;
		}
		
		return true;
	}
}
